package eval

import (
	"showen/lex"
	"showen/obj"
	"showen/parse"
	"testing"
)

func TestTailRecursionOptimization(t *testing.T) {
	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	tests := []struct {
		name     string
		input    string
		expected string
	}{
		{
			name:     "simple tail recursive factorial",
			input:    "(letrec ((fact (lambda (n acc) (if (= n 0) acc (fact (- n 1) (* n acc)))))) (fact 5 1))",
			expected: "120",
		},
		{
			name:     "tail recursive sum",
			input:    "(letrec ((sum (lambda (n acc) (if (= n 0) acc (sum (- n 1) (+ n acc)))))) (sum 10 0))",
			expected: "55",
		},
		{
			name:     "mutual tail recursion - even/odd",
			input:    "(letrec ((even? (lambda (n) (if (= n 0) #t (odd? (- n 1))))) (odd? (lambda (n) (if (= n 0) #f (even? (- n 1)))))) (even? 1000))",
			expected: "#t",
		},
		{
			name:     "tail recursive fibonacci",
			input:    "(letrec ((fib-helper (lambda (n a b) (if (= n 0) a (fib-helper (- n 1) b (+ a b)))))) (fib-helper 10 0 1))",
			expected: "55",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			lexer := lex.NewLexer(tt.input)
			tokens := lexer.ReadTokens()
			parser := parse.NewParser(tokens)
			expr, err := parser.ParseExpression()
			if err != nil {
				t.Fatalf("parser.ParseExpression() error = %v", err)
			}

			result, err := EvalExpressionTrampoline(expr, env)
			if err != nil {
				t.Fatalf("EvalExpressionTrampoline() error = %v", err)
			}

			if result.Inspect() != tt.expected {
				t.Errorf("EvalExpressionTrampoline() = %v, want %v", result.Inspect(), tt.expected)
			}
		})
	}
}

func TestTailCallInIfExpression(t *testing.T) {
	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	// This test ensures that tail calls in if expressions are properly optimized
	input := "(letrec ((test-func (lambda (n) (if (> n 0) (test-func (- n 1)) n)))) (test-func 1000))"

	lexer := lex.NewLexer(input)
	tokens := lexer.ReadTokens()
	parser := parse.NewParser(tokens)
	expr, err := parser.ParseExpression()
	if err != nil {
		t.Fatalf("parser.ParseExpression() error = %v", err)
	}

	result, err := EvalExpressionTrampoline(expr, env)
	if err != nil {
		t.Fatalf("EvalExpressionTrampoline() error = %v", err)
	}

	expected := "0"
	if result.Inspect() != expected {
		t.Errorf("EvalExpressionTrampoline() = %v, want %v", result.Inspect(), expected)
	}
}

func TestTailCallInBeginExpression(t *testing.T) {
	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	// This test ensures that tail calls in begin expressions are properly optimized
	input := "(letrec ((loop (lambda (n) (begin (if (= n 0) n (loop (- n 1))))))) (loop 1000))"

	lexer := lex.NewLexer(input)
	tokens := lexer.ReadTokens()
	parser := parse.NewParser(tokens)
	expr, err := parser.ParseExpression()
	if err != nil {
		t.Fatalf("parser.ParseExpression() error = %v", err)
	}

	result, err := EvalExpressionTrampoline(expr, env)
	if err != nil {
		t.Fatalf("EvalExpressionTrampoline() error = %v", err)
	}

	expected := "0"
	if result.Inspect() != expected {
		t.Errorf("EvalExpressionTrampoline() = %v, want %v", result.Inspect(), expected)
	}
}

func TestTailCallInCondExpression(t *testing.T) {
	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	// This test ensures that tail calls in cond expressions are properly optimized
	input := "(letrec ((test-func (lambda (n) (cond ((= n 0) n) (else (test-func (- n 1))))))) (test-func 1000))"

	lexer := lex.NewLexer(input)
	tokens := lexer.ReadTokens()
	parser := parse.NewParser(tokens)
	expr, err := parser.ParseExpression()
	if err != nil {
		t.Fatalf("parser.ParseExpression() error = %v", err)
	}

	result, err := EvalExpressionTrampoline(expr, env)
	if err != nil {
		t.Fatalf("EvalExpressionTrampoline() error = %v", err)
	}

	expected := "0"
	if result.Inspect() != expected {
		t.Errorf("EvalExpressionTrampoline() = %v, want %v", result.Inspect(), expected)
	}
}
